package com.javawork.juc.synchronize;

import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

/**
 * @author zhangxiaolu
 * @since 2023-02-15 22:37
 */
public class SynchronizedUpDemo {

    /**
     * 问题：锁升级后hash code和GC年龄去哪了？？？
     * 在无锁状态下，Mark Word中可以存储对象的identity hash code值。当对象的hashCode（）方法第一次被调用时，JVM会生成对应的identity hash code值并将该值存储到Mark Word中。
     * <br>
     * 对于偏向锁，在线程获取偏向锁时，会用Thread ID和epoch值覆盖identity hash code所在的位置。如果一个对象的hashCode（）方法已经被调用过一次之后，这个对象不能被设置偏向锁。
     * 因为如果可以的话，那Mark Word中的identity hash code必然会被偏向线程ld给覆盖，这就会造成同一个对象前后两次调用hashCode（）方法得到的结果不一致。
     * <br>
     * 升级为轻量级锁时，JVM会在当前线程的帧中创建一个锁记录（Lock Record）空间，用于存储锁对象的Mark Word拷贝，该拷贝中可以包含identity hash code，
     * 所以轻量级锁可以和identity hash code共存，哈希码和GC年龄自然保存在此，释放锁后会将这些信息写回到对象头。
     * <br>
     * 升级为重量级锁后，Mark Word保存的重量级锁指针，代表重量级锁的ObjectMonitor类里有字段记录非加锁状态下的Mark Word，锁释放后也会将信息写回到对象头。
     *
     * 各种锁的优缺点，synchronized锁升级和实现原理
     * 锁的优缺点的对比
     * 偏向锁：加锁和解锁不需要额外的消耗，和执行非同步方法相比仅存在纳秒级的差距
     *       如果线程间存在锁竞争，会带来额外的锁撤销的消耗
     *       适用于只有一个线程访问同步块场景，适用于单线程适用的情况，在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。
     * 轻量级锁：竞争的线程不会阻塞，提高了程序的响应速度
     *          如果始终得不到锁竞争的线程，使用自旋会消耗CPU
     *          追求响应时间同步块执行速度非常快，适用于竞争较不激烈的情况（这和乐观锁的使用范围类似），存在竞争时升级为轻量级锁，轻量级锁采用的是自旋锁，
     *          如果同步方法/代码块执行时间很短的话，采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。
     * 重量级锁：线程竞争不使用自旋，不会消耗CPU
     *         线程阻塞，响应时间缓慢
     *         追求吞叶量，同步块执行速度较长，适用于竞争激烈的情况，如果同步方法/代码块执行时间很长，那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重，这时候就需要升级为重量级锁。
     * synchronized锁升级过程总结：一句话，就是先自旋，不行再阻塞。
     * 实际上是把之前的悲观锁（重量级锁）变成在一定条件下使用偏向锁以及使用轻量级（自旋锁CAS）的形式
     * synchronized在修饰方法和代码块在字节码上实现方式有很大差异，但是内部实现还是基于对象头的MarkWord来实现的。
     * JDK1.6之前synchronized使用的是重量级锁，JDK1.6之后进行了优化，拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程，而不是无论什么情况都使用重量级锁。
     */

    public static void main(String[] args) {
        //睡5s，保证偏向锁开启
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object obj = new Object();
        System.out.println("本应是偏向锁");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
//        obj.hashCode();//没有重写，一致性哈希，重写后无效，当一个对象已经计算过identity hash code，它就无法进入偏向锁状态；
        synchronized (obj) {
            obj.hashCode();//没有重写，一致性哈希，重写后无效，当一个对象已经计算过identity hash code，它就无法进入偏向锁状态；
//            System.out.println("本应是偏向锁，但是由于计算过一致性哈希，会直接升级为轻量锁");
            System.out.println("偏向锁过程中遇到一致性哈希计算请求，立马撤销偏向模式，膨胀为重量级锁");
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }

    private static void weightLock() {
        /**
         * 重量级锁原理
         * Java中synchronized的重量级锁，是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令，在结束位置插入monitor exit指令。
         * 当线程执行到monitor enter指令时，会尝试获取对象所对应的Monitor所有权，如果获取到了，即获取到了锁，会在Monitor的owner中存放当前线程的id，
         * 这样它将处于锁定状态，除非退出同步块，否则其他线程无法获取到这个Monitor。
         */
        Object obj = new Object();
        new Thread(() -> {
            synchronized (obj) {
                System.out.println(ClassLayout.parseInstance(obj).toPrintable());
            }
        }, "t1").start();
        new Thread(() -> {
            synchronized (obj) {
                System.out.println(ClassLayout.parseInstance(obj).toPrintable());
            }
        }, "t2").start();
    }

    private static void biasedLock() {
        /**
         * 实际上偏向锁在JDK1.6之后是默认开启的，但是启动时间有延迟（4s），所以需要添加参数-XX:BiasedLockingStartupDelay=0，让其在程序启动时立刻启动。
         * 开启偏向锁：也可以先sleep 超过4s后会开启
         * XX：+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
         * 关闭偏向锁：关闭之后程序默认会直接进入---------->>>>>>>>轻量锁状态
         * -XX:-UseBiasedLocking
         */
        Object obj = new Object();
        synchronized (obj) {
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }

    private static void noLock() {
        // OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
        //      0     4        (object header)                           01 42 68 47 (00000001 01000010 01101000 01000111) (1198014977)
        //      4     4        (object header)                           49 00 00 00 (01001001 00000000 00000000 00000000) (73)
        //      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
        //     12     4        (loss due to the next object alignment)
        Object obj = new Object();
        System.out.println("10进制" + obj.hashCode());//10进制:1229416514
        //倒着看，但是每8位是正着看
        System.out.println("16进制" + Integer.toHexString(obj.hashCode()));//16进制:49476842
        System.out.println("2进制" + Integer.toBinaryString(obj.hashCode()));
        // 1001001010001110110100001000010
        //01001001010001110110100001000010
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}